/**
 * See (http://jquery.com/)
 * 
 * @name fn
 * @class See the jQuery Library (http://jquery.com/) for full details. This
 *        just documents the function and classes that are added to jQuery by
 *        this plug-in.
 * @memberOf jQuery
 */

(function(jQuery) {
	// see:
	// http://jqueryhtmltemplates.codeplex.com/documentation?referringTitle=Home

	var JQUERY_STORAGE_BIN="templates";
	
	// loads templates into memory
	jQuery.template = function(source) {
		
		jQuery(source).css("display", "none");

		//we'll store 
		// load any existing template data
		var templateData = jQuery(document).data(JQUERY_STORAGE_BIN) || {};

		// iterate through each source element and add templates
		jQuery(source).children().each(
				function() {

					var template = jQuery(this);

					var prefix = template.parent().attr("data-templateid");
					if (prefix)
						prefix += "_";

					var key = (template.attr("data-templateid") || "").replace(prefix, "");

					if (key) { // ignores elements with no id
						templateData[key] = template;//.removeAttr("id"); 
					} else if (console && console.warn) { 
						console.warn(
								"Encountered a template with no identifier.",
								template);
					}
				});

		jQuery(document).data(JQUERY_STORAGE_BIN, templateData);
		return jQuery;
	};

	/**
	 * Execute a template commend given the templateName and a dictionary of
	 * properties to bind to it.
	 * 
	 * Example: <br/>
	 <pre>
			var $div = $().template("clone", {
				"target":"shoppinglist-nav-item"
			});
			var $entityLink = that.privateMembers.entityLayoutController.getEntityLink(entity);
			$div.template("bind", {
				"_closeButton":function($element) {
					setTimeout(function() {
						$element.unbind("click");
						$element.bind("click", function() {
							Logger.debug("remove entity:" + entity.getGuid());
							shoppingList.removeEntity(entity.getGuid());
							that.publicMethods.saveShoppingList();
							that.publicMethods.displayShoppinglist();
						});
						
					},10);
				},
				"_navItemContent": $entityLink
			});
	 </pre>
	 * @property {String} command the command to execute. Valid values are: [ clone | bind ]
	 * @property {Object} data to bind to the template, or a dictionary of arguments, depending.
	 * @class template
	 * @memberOf jQuery.fn
	 */
	jQuery.fn.template = function(command, args) {

		if (! command) {
			jQuery.template(this);
			return null;
		} else {
			if (command == "clone") {
				if(! args) {
					throw new Error("An arguments dictionary with the 'target' element is required.");
				}
				if (typeof args !== "object") {
					throw new Error("Expected an object as the second argument but got: " + args);
				} 
				if(! args["target"]) {
					throw new Error("An arguments dictionary with the 'target' element is required.");
				}
				
				var templateName = args["target"];
				var templateData = jQuery(document).data(JQUERY_STORAGE_BIN);
				
				if(! templateData) {
					throw new Error("Failed to find the tempaltes, did you call $().template() first?");
				}
				
				var template = templateData[templateName];
				if(! template) {
					throw new Error("Failed to find the template named: " + templateName);
				}
				
				var target = template.clone(true);
				
				$("#templates").append(target);
				
				jQuery.template.populateTemplate(target, args);
				return target;
			} else if (command == "bind") {
				if(!args) {
					throw new Error("Expected data to bind to the template as the second argument.");
				}
				if (typeof args !== "object") {
					throw new Error("Expected data to bind to the template as the second argument but got: " + args);
				} 
				
				var parent = this.parent();
				this.remove();
				var result = jQuery.template.populateTemplate(this, args);
				parent.append(this);
				
				return result;
			} else if(command == "cleanup") {
				$(this).removeAttr("data-templateid");
				$(this).find("*").removeAttr("data-templateid");
				$(this).find("*").removeAttr("data-bindings");
		
			} else {
				throw new Error("Unrecognized template command: " + command);
			}
		}

	};

	function doValueBinding($element, bindingTarget, propertyValue) {
		if (bindingTarget == "this") {
			$element.replaceWith(propertyValue);
		} else if (bindingTarget == "iterator") {
			doIterator($element, bindingTarget, propertyValue);
		} else if (bindingTarget == "innerText") {
			$element.text(propertyValue);
		} else if (bindingTarget == "val") {
			$element.val(propertyValue);
		} else if (bindingTarget == "innerHtml") {
			$element.html(propertyValue);
		} else {
			$element.attr(bindingTarget, propertyValue);
		}
}
	
function doIterator($element, bindingTarget, propertyValue) {
	
	var $parent = $element.parent();
	$element.remove();
	//TODO check for array
	
	for(var i = 0; i < propertyValue.length; i++) {
		var data = propertyValue[i];
		//$element.removeAttr("data-bindings");
		var $target = $element.clone();
		
		jQuery.template.populateTemplateChildren($target, data);
		
		$parent.append($target);
	}
	
}

function doFunctionBinding($element, bindingTarget, func) {
	if(bindingTarget != "this") {
		throw new Error("Can't bind to anything other than this, but got: " + bindingTarget);
	} else {
		func($element);
	}
}

function getValue(bindingName, object) {
	if(object == null) {
		return null;
	}
	var dotIndex = bindingName.indexOf(".");
	if(dotIndex >= 0) {
		var objectName = bindingName.substring(0, dotIndex);
		var remainder = bindingName.substring(dotIndex + 1);
		object = object[objectName];
		return getValue(remainder, object);
	} else {
		return object[bindingName];
	}
}

function doSingleBinding($element, bindingTarget, bindingName, dataToBind) {
	var bindingValue = getValue(bindingName, dataToBind);
	
	
	
	if((typeof bindingValue === 'undefined') 
			|| (bindingValue === null)) {
		return;
	} else if (typeof(bindingValue) == "function") {
		doFunctionBinding($element, bindingTarget, bindingValue);
	} else {
		doValueBinding($element, bindingTarget, bindingValue);
	}
}

function doBindings($element, dataToBind) {
	var bindingAttribute = $element.attr("data-bindings");
	
	if (bindingAttribute) {
		var bindings = bindingAttribute.split(",");
		for ( var i = 0; i < bindings.length; i++) {
			var binding = bindings[i];
			var bindingParts = binding.split(":");
			if(bindingParts.length == 1) {
				//if no : assume that the binding refers to the current element
				doSingleBinding($element, "this", bindingParts[0].trim(), dataToBind);
			} else if(bindingParts.length == 2) {
				doSingleBinding($element, bindingParts[0].trim(), bindingParts[1].trim(), dataToBind);
				
			} else {
				throw new Error("Expected bingings to be in the format of <target>:<name> but found: " + binding);
			}
			
		}

	}
}

	jQuery.template.populateTemplateChildren = function(target, dataToBind) {
	
		target.find("[data-bindings]").each(function() {
			doBindings($(this), dataToBind);
			jQuery.template.populateTemplate( $(this), dataToBind);
		});

		return target;
	};
	
	/**
	 * Given a target element, a template, and dataToBind
	 * 
	 */
	jQuery.template.populateTemplate = function(target, dataToBind) {
		
		doBindings(target, dataToBind);
		jQuery.template.populateTemplateChildren(target, dataToBind);

		return target;
	};
})(jQuery);